package com.canyou.activiti.service.impl;

import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.canyou.activiti.entity.DeploymentVO;
import com.canyou.activiti.entity.FormDataItemDTO;
import com.canyou.activiti.entity.ProcessDefinitionVO;
import com.canyou.activiti.mapper.DeploymentMapper;
import com.canyou.activiti.mapper.FormDataItemMapper;
import com.canyou.activiti.mapper.FormDataMapper;
import com.canyou.activiti.model.FormData;
import com.canyou.activiti.model.FormDataItem;
import com.canyou.activiti.service.ProcessDefinitionService;
import com.canyou.basic.model.Media;
import com.canyou.basic.service.MediaService;
import com.canyou.enums.ErrorCodeEnum;
import com.canyou.enums.MediaTypeEnum;
import com.canyou.exception.CysBusinessException;
import com.canyou.utils.JsonUtils;
import com.canyou.utils.StringUtils;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.TaskService;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntityImpl;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipInputStream;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author canyou
 * @since 2021-03-02
 */
@Service
public class ProcessDefinitionServiceImpl implements ProcessDefinitionService {

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private DeploymentMapper deploymentMapper;

    @Autowired
    private MediaService mediaService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private FormDataMapper formDataMapper;

    @Autowired
    private FormDataItemMapper formDataItemMapper;

    @Override
    public IPage<ProcessDefinitionVO> getPageList(IPage<ProcessDefinitionVO> page, String processName, String processKey) {
        ProcessDefinitionQuery processDefinitionQuery = repositoryService
                .createProcessDefinitionQuery()
                .orderByProcessDefinitionVersion().desc()
                .orderByProcessDefinitionId().asc();
        if (StringUtils.isNotBlank(processName)) {
            processDefinitionQuery.processDefinitionNameLike("%" + processName + "%");
        }
        if (StringUtils.isNotBlank(processKey)) {
            processDefinitionQuery.processDefinitionKeyLike("%" + processKey + "%");
        }
        // 查询records
        List<ProcessDefinition> processDefinitionList = processDefinitionQuery.listPage((int) ((page.getCurrent() - 1) * page.getSize()), (int) page.getSize());
        List<ProcessDefinitionVO> list = new ArrayList<>();
        if (processDefinitionList != null && !processDefinitionList.isEmpty()) {
            List<DeploymentVO> deploymentList = deploymentMapper.selectActReDeploymentByIds(processDefinitionList.parallelStream()
                    .map(ProcessDefinition::getDeploymentId)
                    .collect(Collectors.toList()));
            list = processDefinitionList.parallelStream()
                    .map(pd -> new ProcessDefinitionVO()
                            .setId(pd.getId())
                            .setKey(pd.getKey())
                            .setName(pd.getName())
                            .setDescription(pd.getDescription())
                            .setDeploymentId(pd.getDeploymentId())
                            .setVersion(pd.getVersion())
                            .setSuspensionState(((ProcessDefinitionEntityImpl) pd).getSuspensionState())
                            .setResourceName(pd.getResourceName())
                            .setDeployTime(deploymentList.parallelStream()
                                    .filter(d -> d.getId().equals(pd.getDeploymentId()))
                                    .findFirst()
                                    .orElseGet(DeploymentVO::new)
                                    .getDeployTime()))
                    .collect(Collectors.toList());
        }
        page.setRecords(list);
        // 查询总条数
        page.setTotal(processDefinitionQuery.count());
        return page;
    }

    @Override
    public void deleteProcessDefinition(String deploymentId) {
        // 查询该部署是否还有待办任务, 如果存在, 则无法删除
        if (taskService.createTaskQuery().deploymentId(deploymentId).count() > 0) {
            throw new CysBusinessException(ErrorCodeEnum.ILLEGAL, "该部署还有未完结的任务");
        }
        // 删除级联的其他数据
        repositoryService.deleteDeployment(deploymentId, true);
    }

    @Override
    public void activeOrSuspendProcessDefinition(String processDefinitionId, Integer suspensionState) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(processDefinitionId)
                .singleResult();
        // 获取流程定义, 如果不存在则异常
        if (processDefinition == null) {
            throw new CysBusinessException(ErrorCodeEnum.ILLEGAL, "部署流程不存在");
        }
        int orgSuspensionState = ((ProcessDefinitionEntityImpl) processDefinition).getSuspensionState();
        // 如果要变成的状态与当前状态一致, 那么抛出异常
        if (suspensionState.equals(orgSuspensionState)) {
            throw new CysBusinessException(ErrorCodeEnum.ILLEGAL,
                    StringUtils.format("当前已经是{}状态", Integer.valueOf(1).equals(suspensionState) ? "激活" : "挂起"));
        }
        if (Integer.valueOf(1).equals(suspensionState)) {
            repositoryService.activateProcessDefinitionById(processDefinitionId);
        } else {
            repositoryService.suspendProcessDefinitionById(processDefinitionId);
        }
    }

    @Override
    public DeploymentVO deployFile(Long mediaId, String processDefinitionName) {
        Media media = mediaService.getById(mediaId);
        if (media == null) {
            throw new CysBusinessException(ErrorCodeEnum.ILLEGAL, "部署文件不存在");
        }
        MediaTypeEnum mediaTypeEnum = MediaTypeEnum.valueOf(media.getMediaType());
        InputStream fileInputStream = null;
        try {
            String filePath = mediaService.getUploadAbsolutePath(media.getMediaPath());
            fileInputStream = FileUtil.getInputStream(filePath);
        } catch (FileNotFoundException e) {
            throw new CysBusinessException(ErrorCodeEnum.ILLEGAL, "读取部署文件异常", e);
        }
        Deployment deployment;
        // 初始化流程
        if (MediaTypeEnum.ZIP.equals(mediaTypeEnum)) {
            ZipInputStream zip = new ZipInputStream(fileInputStream);
            deployment = repositoryService.createDeployment()
                    .addZipInputStream(zip)
                    .name(StringUtils.defaultString(processDefinitionName))
                    .deploy();
        } else {
            deployment = repositoryService.createDeployment()
                    .addInputStream(media.getOriginalMediaName(), fileInputStream)
                    .name(StringUtils.defaultString(processDefinitionName))
                    .deploy();
        }
        return new DeploymentVO()
                .setId(deployment.getId())
                .setName(deployment.getName())
                .setDeployTime(deployment.getDeploymentTime());
    }

    @Override
    public void getXml(HttpServletResponse response, String deploymentId, String resourceName) {
        try (InputStream in = repositoryService.getResourceAsStream(deploymentId, resourceName);
             ServletOutputStream out = response.getOutputStream()) {
            IOUtils.copy(in, out);
            response.flushBuffer();
        } catch (Exception ex) {
            throw new CysBusinessException(ErrorCodeEnum.ILLEGAL, "读取流程xml文件异常", ex);
        }
    }

    @Override
    public Deployment addDeploymentByString(String bpmnStr) {
        Deployment deployment = repositoryService.createDeployment()
                .addString(StringUtils.format("CreateWithBPMNJS-{}.bpmn", String.valueOf(System.currentTimeMillis())), bpmnStr)
                .deploy();
        if (deployment != null) {
            initBpmnModel(repositoryService
                    .createProcessDefinitionQuery()
                    .deploymentId(deployment.getId())
                    .singleResult()
                    .getId());
        }
        return deployment;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void initBpmnModel(String processDefinitionId) {
        repositoryService.getBpmnModel(processDefinitionId).getProcesses().stream().forEach(process -> {
            process.getFlowElements().stream().forEach(flowElement -> {
                if (flowElement instanceof UserTask) {
                    // 构建表单主表
                    UserTask userTaskElement = (UserTask) flowElement;
                    if (StringUtils.isNotBlank(userTaskElement.getFormKey())) {

                        FormData formData = new FormData();
                        formData.setProcDefId(processDefinitionId);
                        formData.setFormId(userTaskElement.getId());
                        formData.setFormKey(userTaskElement.getFormKey());
                        formData.setFormName(userTaskElement.getName());
                        formDataMapper.insert(formData);

                        // 构建表单项
                        userTaskElement.getFormProperties().stream().forEach(formProperty -> {
                            String propJson = formProperty.getId();
                            FormDataItemDTO formDataItemDTO;
                            if (StringUtils.isNotBlank(propJson)) {
                                formDataItemDTO = JsonUtils.toObject(propJson, FormDataItemDTO.class);

                                FormDataItem formDataItem = new FormDataItem();
                                formDataItem.setFormDataId(formData.getId());
                                formDataItem.setControlId(formDataItemDTO.getControlId());
                                formDataItem.setDataType(formDataItemDTO.getDataType());
                                formDataItem.setControlType(formDataItemDTO.getControlType());
                                formDataItem.setControlName(formDataItemDTO.getControlName());
                                formDataItem.setDefaultVal(formDataItemDTO.getDefaultVal());
                                formDataItem.setOptionalVal(formDataItemDTO.getOptionalVal());
                                formDataItem.setPlaceHolder(formDataItemDTO.getPlaceHolder());
                                formDataItem.setValidateConstraint(formDataItemDTO.getValidateConstraint());
                                formDataItem.setIsParam(formDataItemDTO.getIsParam() == null ||
                                        Boolean.FALSE.equals(formDataItemDTO.getIsParam()) ? 0 : 1);
                                formDataItem.setIsRequired(formDataItemDTO.getIsRequired() == null ||
                                        Boolean.FALSE.equals(formDataItemDTO.getIsRequired()) ? 0 : 1);
                                formDataItemMapper.insert(formDataItem);
                            }
                        });
                    }
                }
            });
        });
    }
}
